# Copyright (c) 2019 Presto Labs Pte. Ltd.

# Author: xguo

import time
import functools

from collections import namedtuple
from coin.exchange.kr_rest.product_holders import get_holder_from_product
from coin.exchange.base.kr_rest.product_info import ProductInfo
from coin.base.book.util import (
    convert_book_to_dict,
    convert_book_to_json,
)

PassivePrices = namedtuple('PassivePrices',
                           ['askp', 'bidp', 'askp_pull', 'bidp_pull', 'edge_buy', 'edge_sell'])

AggressivePrices = namedtuple('AggressivePrices', ['true_book_price', 'posting_price', 'edge'])

default_product_info = ProductInfo.gen3(price_ticksize=1e-8, qty_precision=1e-8)


def get_timestamp():
  return int(time.time() * 1e9)


def get_tick(product):
  try:
    return get_holder_from_product(product).get_tick()
  except (KeyError, ValueError):
    return default_product_info.gen_tick()


class SingleProductPriceCalculator(object):
  def __init__(self, product):
    self.timestamp = 0
    self.product = product
    self.ask0 = 0.
    self.bid0 = 0.
    self.askt = 0.
    self.bidt = 0.
    self.midp = 0.
    self.midt = 0.
    self.ask0_qty = 0.
    self.bid0_qty = 0.
    self.ask0_minus_one = 0.
    self.bid0_plus_one = 0.
    self._price_det_notional = 0
    self._tick = get_tick(product)
    self._book = None

  def renew_tick(self):
    self._tick = get_tick(self.product)

  def is_ready(self):
    delta_ts = get_timestamp() - self.timestamp
    return delta_ts < 30e9

  @property
  def price_ticksize(self):
    p1 = self._tick.round(self.ask0)
    p2 = self._tick.get_next_price(p1)
    return p2 - p1

  @property
  def price_ticksize_ratio(self):
    return self.price_ticksize / self.midp

  @property
  def price_det_notional(self):
    return self._price_det_notional

  def set_price_det_notional(self, price_det_notional):
    self._price_det_notional = price_det_notional

  def _book_askt_func(self, book):
    if hasattr(book, 'get_notional_asks_by_amt'):
      return book.get_notional_asks_by_amt(self._price_det_notional).entries[-1].price
    elif hasattr(book, 'get_ask_price_by_amount'):
      return book.get_ask_price_by_amount(self._price_det_notional)
    else:
      return book.ask0().price

  def _book_bidt_func(self, book):
    if hasattr(book, 'get_notional_bids_by_amt'):
      return book.get_notional_bids_by_amt(self._price_det_notional).entries[-1].price
    elif hasattr(book, 'get_bid_price_by_amount'):
      return book.get_bid_price_by_amount(self._price_det_notional)
    else:
      return book.bid0().price

  def update(self, book):
    self.timestamp = book.timestamp

    if not (book.has_ask() and book.has_bid()):
      self.reset()
      return

    self.ask0 = book.ask0().price
    self.bid0 = book.bid0().price
    self.ask0_qty = book.ask0().qty
    self.bid0_qty = book.bid0().qty
    self.askt = self._book_askt_func(book)
    self.bidt = self._book_bidt_func(book)
    self.midp = (self.ask0 + self.bid0) / 2
    self.midt = (self.askt + self.bidt) / 2
    self.spread = self.ask0 - self.bid0

    assert self.askt >= self.ask0 >= self.midp >= self.bid0 >= self.bidt, (self.askt,
                                                                           self.ask0,
                                                                           self.midp,
                                                                           self.bid0,
                                                                           self.bidt)

    # Cut-in-line
    self.ask0_minus_one = self._tick.get_prev_price(self.ask0)
    self.bid0_plus_one = self._tick.get_next_price(self.bid0)

    self.ready = True
    self._book = book

  def get_bid_array(self, n):
    return self._book.get_bid_array(n)

  def get_ask_array(self, n):
    return self._book.get_ask_array(n)

  @property
  def ask1(self):
    return self.get_ask_array(2)[1][0]

  @property
  def ask1_qty(self):
    return self.get_ask_array(2)[1][1]

  @property
  def bid1(self):
    return self.get_bid_array(2)[1][0]

  @property
  def bid1_qty(self):
    return self.get_bid_array(2)[1][1]

  def get_prev_price(self, price):
    return self._tick.get_prev_price(self._tick.round(price))

  def get_next_price(self, price):
    return self._tick.get_next_price(self._tick.round(price))

  def round_price(self, price):
    return self._tick.round(price)

  def correct_ask_passive(self, ask_orig, cut_in_line=False):
    askp = self._tick.ceil(ask_orig)
    if cut_in_line:
      askp = max(askp, self.ask0_minus_one, self.bid0_plus_one)
    return askp

  def correct_bid_passive(self, bid_orig, cut_in_line=False):
    bidp = self._tick.floor(bid_orig)
    if cut_in_line:
      bidp = min(bidp, self.ask0_minus_one, self.bid0_plus_one)
    return bidp

  def correct_ask_aggressive(self, ask_orig):
    return self._tick.get_prev_price(ask_orig)

  def correct_bid_aggressive(self, bid_orig):
    return self._tick.get_next_price(bid_orig)

  @staticmethod
  def convert_to_dict(book, level=2, notional_qty=None, notional_amount=None):
    return convert_book_to_dict(book,
                                level=level,
                                notional_qty=notional_qty,
                                notional_amount=notional_amount)

  @staticmethod
  def convert_to_json(book, level=2, notional_qty=None, notional_amount=None):
    return convert_book_to_json(book,
                                level=level,
                                notional_qty=notional_qty,
                                notional_amount=notional_amount)


if __name__ == '__main__':
  from coin.exchange.bithumb.kr_rest.product import BithumbProduct
  prod = BithumbProduct.FromStr('ADA-KRW')
  pc = SingleProductPriceCalculator(prod)
  print(pc.get_prev_price(1.2111))
